﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyScaner
{
    class Scaner
    {
        private string _input;
        private int _pos = 0;
        private Token _nextToken;
        private Dictionary<string, KIND> _keywords;

        public Scaner(string input)
        {
            InitKeywords();
            _input = input;
            _nextToken = Read();
        }
        public Token Next()
        {
            Token token = _nextToken;
            _nextToken = Read();
            return token;
        }

        public Token Peek()
        {
            return _nextToken;
        }

        private void InitKeywords()
        {
            _keywords = new Dictionary<string, KIND>();
            _keywords.Add("abstract", KIND.ABSTRACT);	
            _keywords.Add("do", KIND.DO);	
            _keywords.Add("in", KIND.IN);	
            _keywords.Add("protected", KIND.PROTECTED);	
            _keywords.Add("true", KIND.TRUE);
            _keywords.Add("as", KIND.AS);	
            _keywords.Add("double", KIND.DOUBLE);	
            _keywords.Add("int", KIND.INT);	
            _keywords.Add("public", KIND.PUBLIC);	
            _keywords.Add("try", KIND.TRY);
            _keywords.Add("base", KIND.BASE);	
            _keywords.Add("else", KIND.ELSE);	
            _keywords.Add("interface", KIND.INTERFACE);	
            _keywords.Add("readonly", KIND.READONLY);	
            _keywords.Add("typeof", KIND.TYPEOF);
            _keywords.Add("bool", KIND.BOOL);	
            _keywords.Add("enum", KIND.ENUM);	
            _keywords.Add("internal", KIND.INTERNAL);	
            _keywords.Add("ref", KIND.REF);	
            _keywords.Add("uint", KIND.UINT);
            _keywords.Add("break", KIND.BREAK);	
            _keywords.Add("event", KIND.EVENT);	
            _keywords.Add("is", KIND.IS);	
            _keywords.Add("return", KIND.RETURN);	
            _keywords.Add("ulong", KIND.ULONG);
            _keywords.Add("byte", KIND.BYTE);	
            _keywords.Add("explicit", KIND.EXPLICIT);	
            _keywords.Add("lock", KIND.LOCK);	
            _keywords.Add("sbyte", KIND.SBYTE);	
            _keywords.Add("unchecked", KIND.UNCHECKED);
            _keywords.Add("case", KIND.CASE);	
            _keywords.Add("extern", KIND.EXTERN);	
            _keywords.Add("long", KIND.LONG);	
            _keywords.Add("sealed", KIND.SEALED);	
            _keywords.Add("unsafe", KIND.UNSAFE);
            _keywords.Add("catch", KIND.CATCH);	
            _keywords.Add("false", KIND.FALSE);	
            _keywords.Add("namespace", KIND.NAMESPACE);	
            _keywords.Add("short", KIND.SHORT);	
            _keywords.Add("ushort", KIND.USHORT);
            _keywords.Add("char", KIND.CHAR_KEYWORD);	
            _keywords.Add("finally", KIND.FINALLY);	
            _keywords.Add("new", KIND.NEW);	
            _keywords.Add("sizeof", KIND.SIZEOF);	
            _keywords.Add("using", KIND.USING);
            _keywords.Add("checked", KIND.CHECKED);	
            _keywords.Add("fixed", KIND.FIXED);	
            _keywords.Add("null", KIND.NULL);	
            _keywords.Add("stackalloc", KIND.STACKALLOC);	
            _keywords.Add("virtual", KIND.VIRTUAL);
            _keywords.Add("class", KIND.CLASS);	
            _keywords.Add("float", KIND.FLOAT);	
            _keywords.Add("object", KIND.OBJECT);	
            _keywords.Add("static", KIND.STATIC);	
            _keywords.Add("void", KIND.VOID);
            _keywords.Add("const", KIND.CONST);	
            _keywords.Add("for", KIND.FOR);	
            _keywords.Add("operator", KIND.OPERATOR);	
            _keywords.Add("string", KIND.STRING_KEYWORD);	
            _keywords.Add("volatile", KIND.VOLATILE);
            _keywords.Add("continue", KIND.CONTINUE);	
            _keywords.Add("foreach", KIND.FOREACH);	
            _keywords.Add("out", KIND.OUT);	
            _keywords.Add("struct", KIND.STRUCT);	
            _keywords.Add("while", KIND.WHILE);
            _keywords.Add("decimal", KIND.DECIMAL);	
            _keywords.Add("goto", KIND.GOTO);	
            _keywords.Add("override", KIND.OVERRIDE);	
            _keywords.Add("switch", KIND.SWITCH);
            _keywords.Add("default", KIND.DEFAULT);	
            _keywords.Add("if", KIND.IF);	
            _keywords.Add("params", KIND.PARAMS);	
            _keywords.Add("this", KIND.THIS);
            _keywords.Add("delegate", KIND.DELEGATE);	
            _keywords.Add("implicit", KIND.IMPLICIT);	
            _keywords.Add("private", KIND.PRIVATE);	
            _keywords.Add("throw", KIND.THROW);

        }

        private Token Read()
        {
            SkipWhiteSpaces();
            char ch = ReadChar();
            switch (ch)
            {
                case (char)0: return new Token(KIND.EOS, "");
                case ';': return new Token(KIND.SEMICOLON, ";");
                case '{': return new Token(KIND.LEFT_BRACE, "{");
                case '}': return new Token(KIND.RIGHT_BRACE, "}");
                case '(': return new Token(KIND.LEFT_BRACKET, "(");
                case ')': return new Token(KIND.RIGHT_BRACKET, ")");
                case '\t': return new Token(KIND.TAB, "\\t");
                case '.': return new Token(KIND.POINT, ".");
                case ',': return new Token(KIND.COMMA, ",");
                case '+':
                    {
                        char nextCh = PeekChar();
                        switch (nextCh)
                        {
                            case '+': ++_pos; return new Token(KIND.INCREMENT, "++");
                            case '=': ++_pos; return new Token(KIND.ASSIGN_SUMM, "+=");
                            default: return new Token(KIND.SUMM, "+");
                        }
                    }
                case '-':
                    {
                        char nextCh = PeekChar();
                        switch (nextCh)
                        {
                            case '-': ++_pos; return new Token(KIND.DECREMENT, "--");
                            case '=': ++_pos; return new Token(KIND.ASSIGN_SUB, "-=");
                            default: return new Token(KIND.SUBTRACTION, "-");
                        }
                    }
                case '=':
                    {
                        char nextCh = PeekChar();
                        switch (nextCh)
                        {
                            case '=': ++_pos; return new Token(KIND.EQUAL, "==");
                            default: return new Token(KIND.ASSIGN, "=");
                        }
                    }
                case '!':
                    {
                        char nextCh = PeekChar();
                        switch (nextCh)
                        {
                            case '=': ++_pos; return new Token(KIND.NOT_EQUAL, "!=");
                            default: return new Token(KIND.DENIAL, "!");
                        }
                    }
                case '<':
                    {
                        char nextCh = PeekChar();
                        switch (nextCh)
                        {
                            case '=': ++_pos; return new Token(KIND.LESS_EQUAL, "<=");
                            default: return new Token(KIND.LESS, "<");
                        }
                    }
                case '>':
                    {
                        char nextCh = PeekChar();
                        switch (nextCh)
                        {
                            case '=': ++_pos; return new Token(KIND.MORE_EQUAL, ">=");
                            default: return new Token(KIND.MORE, ">");
                        }
                    }
                case '/':
                    {
                        char nextCh = PeekChar();
                        switch (nextCh)
                        {
                            case '/': _pos = _input.Length; return new Token(KIND.STRING_COMMENT, "//");
                            case '*':
                                {
                                    char nextNextChar =ReadChar();
                                    string val = "/*";
                                    val += nextNextChar;
                                    while (!(nextNextChar == '/' && nextCh == '*') && nextNextChar != (char)0)
                                    {
                                        nextCh = ReadChar();
                                        nextNextChar = PeekChar();
                                        val += nextNextChar;
                                    }
                                    if (nextNextChar != (char)0)
                                    {
                                        ReadChar();
                                        return new Token(KIND.COMMENT, val);
                                    }
                                    else
                                    {
                                        return new Token(KIND.UNKNOWN, "bad comment"); // errors handling will be changed
                                    }

                                }
                            default: return new Token(KIND.UNKNOWN, "unexpected /"); // errors handling will be changed
                        }
                    }
                case '\'':
                    {
                        char nextCh = ReadChar();
                        string val = "\'";
                        val += nextCh;
                        while ( !(nextCh == '\'' || nextCh == (char)0) )
                        {
                            nextCh = ReadChar();
                            val += nextCh;
                        }
                        if (val.Length == 3 && nextCh != (char)0)
                        {
                            return new Token(KIND.CHAR, val);
                        }
                        else
                        {
                            return new Token(KIND.UNKNOWN, "bad char"); // errors handling will be changed
                        }
                    }
                case '"':
                    {
                        char nextCh = ReadChar();
                        string val = "\"";
                        val += nextCh;
                        while (!(nextCh == '\"' || nextCh == (char)0))
                        {
                            nextCh = ReadChar();
                            val += nextCh;
                        }
                        if (nextCh != (char)0)
                        {
                            return new Token(KIND.STRING, val);
                        }
                        else
                        {
                            return new Token(KIND.UNKNOWN, "bad string"); // errors handling will be changed
                        }
                    }
                default:
                    {
                        if (char.IsDigit(ch))
                        {
                            string val = "";
                            val += ch;
                            char nextCh = PeekChar();
                            while (char.IsDigit(nextCh))
                            {
                                val += ReadChar();
                                nextCh = PeekChar();
                            }
                            return new Token(KIND.DIGIT, val);
                        }
                        else if (IsFirstIdentefierChar(ch))
                        {
                            string val = "";
                            val += ch;
                            char nextCh = PeekChar();
                            while (IsIdentefierChar(nextCh))
                            {
                                val += ReadChar();
                                nextCh = PeekChar();
                            }
                            KIND? keyword = IsKeyword(val);
                            if (keyword != null)
                            {
                                return new Token((KIND)keyword, val);
                            }
                            else
                            {
                                return new Token(KIND.IDENTIFIER, val);
                            }
                        }
                        else
                        {
                            return new Token(KIND.UNKNOWN, "");
                        }
                    }
            }
        }

        private KIND? IsKeyword(string str)
        {
            KIND? result = null;
            var selectKeyword = from entry in _keywords
                                  where entry.Key == str
                                  select entry.Value;
            if (selectKeyword.Count() > 0)
            {
                result = selectKeyword.First();
            }
            return result;
        }

        private bool IsFirstIdentefierChar(char ch)
        {
            bool result = false;
            if (char.IsLetter(ch) || ch == '_')
            {
                result = true;
            }
            return result;
        }

        private bool IsIdentefierChar(char ch)
        {
            return IsFirstIdentefierChar(ch) || char.IsDigit(ch);
        }

        private char ReadChar()
        {
            char ch = PeekChar();
            if (ch != (char)0)
            {
                ++_pos;
            }
            return ch;
        }

        private char PeekChar()
        {
            return (_pos >= _input.Length) ? (char)0 : _input[_pos];
        }

        private void SkipWhiteSpaces()
        {
            char ch = PeekChar();
            while (ch == ' ' && ch != (char)0)
            {
                ++_pos;
                ch = PeekChar();
            }
        }
    }
}
